/*

    mii: monitor and control the MII for a network interface

    Usage:

	mii-tool [-VvRrw] [-A media,... | -F media] [interface ...]

    This program is based on Donald Becker's "mii-diag" program, which
    is more capable and verbose than this tool, but also somewhat
    harder to use.

    Copyright (C) 2000 David A. Hinds -- dhinds@pcmcia.sourceforge.org

    mii-diag is written/copyright 1997-2000 by Donald Becker
        <becker@scyld.com>

    This program is free software; you can redistribute it
    and/or modify it under the terms of the GNU General Public
    License as published by the Free Software Foundation.

    Donald Becker may be reached as becker@scyld.com, or C/O
    Scyld Computing Corporation, 410 Severn Av., Suite 210,
    Annapolis, MD 21403

    References
	http://www.scyld.com/diag/mii-status.html
	http://www.scyld.com/expert/NWay.html
	http://www.national.com/pf/DP/DP83840.html
*/

static char version[] =
"mii-tool.c 1.9 2000/04/28 00:56:08 (David Hinds)\n";

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <time.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#ifndef __GLIBC__
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#endif
#include "mii.h"

#define MAX_ETH		8		/* Maximum # of interfaces */

/* Table of known MII's */
static struct {
    u_short	id1, id2;
    char	*name;
} mii_id[] = {
    { 0x0022, 0x5610, "AdHoc AH101LF" },
    { 0x0022, 0x5520, "Altimata AC101LF" },
    { 0x0000, 0x6b90, "AMD 79C901A HomePNA" },
    { 0x0000, 0x6b70, "AMD 79C901A 10baseT" },
    { 0x0181, 0xb800, "Davicom DM9101" },
    { 0x0043, 0x7411, "Enable EL40-331" },
    { 0x0015, 0xf410, "ICS 1889" },
    { 0x0015, 0xf420, "ICS 1890" },
    { 0x0015, 0xf430, "ICS 1892" },
    { 0x02a8, 0x0150, "Intel 82555" },
    { 0x7810, 0x0000, "Level One LXT970/971" },
    { 0x2000, 0x5c00, "National DP83840A" },
    { 0x0181, 0x4410, "Quality QS6612" },
    { 0x0282, 0x1c50, "SMSC 83C180" },
    { 0x0300, 0xe540, "TDK 78Q2120" },
};
#define NMII (sizeof(mii_id)/sizeof(mii_id[0]))

/*--------------------------------------------------------------------*/

static unsigned int
    verbose = 0,
    opt_version = 0,
    opt_restart = 0,
    opt_reset = 0,
    opt_log = 0,
    opt_watch = 0;
static int nway_advertise = 0;
static int fixed_speed = 0;
static int override_phy = -1;

static int skfd = -1;		/* AF_INET socket for ioctl() calls. */
static struct ifreq ifr;

/*--------------------------------------------------------------------*/

static int mdio_read(int skfd, int location)
{
    struct mii_data *mii = (struct mii_data *)&ifr.ifr_data;
    mii->reg_num = location;
    if (ioctl(skfd, SIOCGMIIREG, &ifr) < 0) {
	fprintf(stderr, "SIOCGMIIREG on %s failed: %s", ifr.ifr_name,
		strerror(errno));
	return -1;
    }
    return mii->val_out;
}

static void mdio_write(int skfd, int location, int value)
{
    struct mii_data *mii = (struct mii_data *)&ifr.ifr_data;
    mii->reg_num = location;
    mii->val_in = value;
    if (ioctl(skfd, SIOCSMIIREG, &ifr) < 0) {
	fprintf(stderr, "SIOCSMIIREG on %s failed: %s", ifr.ifr_name,
		strerror(errno));
    }
}

/*--------------------------------------------------------------------*/

const struct {
    char	*name;
    u_short	value;
} media[] = {
    /* The order through 100baseT4 matches bits in the BMSR */
    { "10baseT-HD",	MII_AN_10BASET_HD },
    { "10baseT-FD",	MII_AN_10BASET_FD },
    { "100baseTx-HD",	MII_AN_100BASETX_HD },
    { "100baseTx-FD",	MII_AN_100BASETX_FD },
    { "100baseT4",	MII_AN_100BASET4 },
    { "100baseTx",	MII_AN_100BASETX_FD | MII_AN_100BASETX_HD },
    { "10baseT",	MII_AN_10BASET_FD | MII_AN_10BASET_HD },
};
#define NMEDIA (sizeof(media)/sizeof(media[0]))
	
/* Parse an argument list of media types */
static int parse_media(char *arg)
{
    int mask, i;
    char *s;
    mask = strtoul(arg, &s, 16);
    if ((*arg != '\0') && (*s == '\0')) {
	if ((mask & MII_AN_ABILITY_MASK) &&
	    !(mask & ~MII_AN_ABILITY_MASK))
	    return mask;
	goto failed;
    } else {
	mask = 0;
	s = strtok(arg, ", ");
	do {
	    for (i = 0; i < NMEDIA; i++)
		if (strcasecmp(media[i].name, s) == 0) break;
	    if (i == NMEDIA) goto failed;
	    mask |= media[i].value;
	} while ((s = strtok(NULL, ", ")) != NULL);
    }
    return mask;
failed:
    fprintf(stderr, "Invalid media specification '%s'.", arg);
    return -1;
}

/*--------------------------------------------------------------------*/

static char *media_list(int mask, int best)
{
    static char buf[100];
    int i;
    *buf = '\0';
    mask >>= 5;
    for (i = 4; i >= 0; i--) {
	if (mask & (1<<i)) {
	    strcat(buf, " ");
	    strcat(buf, media[i].name);
	    if (best) break;
	}
    }
    if (mask & (1<<5))
	strcat(buf, " flow-control");
    return buf;
}

char *show_basic_mii(int sock, int phy_id)
{
    static char buf[100];
    int i, mii_val[32];
    int bmcr, bmsr, advert, lkpar;

    /* Some bits in the BMSR are latched, but we can't rely on being
       the only reader, so only the current values are meaningful */
    mdio_read(sock, MII_BMSR);
    for (i = 0; i < ((verbose > 1) ? 32 : 8); i++)
	mii_val[i] = mdio_read(sock, i);

    if (mii_val[MII_BMCR] == 0xffff) {
	sprintf((char *)&buf, "  No MII transceiver present!.");
	return (char *)&buf;
    }

    /* Descriptive rename. */
    bmcr = mii_val[MII_BMCR]; bmsr = mii_val[MII_BMSR];
    advert = mii_val[MII_ANAR]; lkpar = mii_val[MII_ANLPAR];

    sprintf(buf, "%s: ", ifr.ifr_name);
    if (bmcr & MII_BMCR_AN_ENA) {
	if (bmsr & MII_BMSR_AN_COMPLETE) {
	    if (advert & lkpar) {
		strcat(buf, (lkpar & MII_AN_ACK) ?
		       "negotiated" : "no autonegotiation,");
		strcat(buf, media_list(advert & lkpar, 1));
		strcat(buf, ", ");
	    } else {
		strcat(buf, "autonegotiation failed, ");
	    }
	} else if (bmcr & MII_BMCR_RESTART) {
	    strcat(buf, "autonegotiation restarted, ");
	}
    } else {
	sprintf(buf+strlen(buf), "%s Mbit, %s duplex, ",
	       (bmcr & MII_BMCR_100MBIT) ? "100" : "10",
	       (bmcr & MII_BMCR_DUPLEX) ? "full" : "half");
    }
    strcat(buf, (bmsr & MII_BMSR_LINK_VALID) ? "link ok" : "no link");

    return (char *)&buf;
}

/*--------------------------------------------------------------------*/

static char *do_one_xcvr(int skfd, const char *ifname, int maybe)
{
    static char output[200];
    struct mii_data *mii = (struct mii_data *)&ifr.ifr_data;

    /* Get the vitals from the interface. */
    strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
    if (ioctl(skfd, SIOCGMIIPHY, &ifr) < 0) {
	if (!maybe || (errno != ENODEV))
	    sprintf((char *)&output, "SIOCGMIIPHY on '%s' failed: %s",
		    ifname, strerror(errno));
	return (char *)&output;
    }

    if (override_phy >= 0) {
	mii->phy_id = override_phy;
    }

    return show_basic_mii(skfd, mii->phy_id);
}

char *miiquery(const char *arg)
{
    int i, c, ret, errflag = 0;
    char s[6];
    char *str;
    static char output[200];
    
    /* Open a basic socket. */
    if ((skfd = socket(AF_INET, SOCK_DGRAM,0)) < 0) {
	    sprintf((char *)&output, "Unable to open socket: %s", strerror(errno));
	    return(char *)&output;
    }

    str=do_one_xcvr(skfd, arg, 0);
    close(skfd);
    return str;
}

/*
 * I have turned this program from the hacking tool into simple utility
 * that allows you to send gratuitous arp requests for the interface.
 * It will pick up the MAC and IP from the given interface and advertise
 * it to the network. It works with alias interfaces as well.
 * This is useful in all sorts of failover situations where
 * - One interface completely takes over the MAC of another
 * - Alias IP is assigned to a different interface and has to be
 *   advertised as such.
 * 
 * I didn't do much - just added a couple of lines of code. It only
 * works verifiably on Linux.
 * Questions probably should go to original author, but if you really
 * want you can talk to me as well. Ths program comes as a free software
 * to be distributed in any form, binary or source without warranty
 * of any kind. Use at your own risk.
 *
 * To compile:
 * 	gcc -o send_arp send_arp.c
 *
 * 2001 (c) Ugen ugen@xonix.com
 */ 

/* send_arp.c

This program sends out one ARP packet with source/target IP and Ethernet
hardware addresses suuplied by the user.  It compiles and works on Linux
and will probably work on any Unix that has SOCK_PACKET.

The idea behind this program is a proof of a concept, nothing more.  It
comes as is, no warranty.  However, you're allowed to use it under one
condition: you must use your brain simultaneously.  If this condition is
not met, you shall forget about this program and go RTFM immediately.

yuri volobuev'97
volobuev@t1.chem.umn.edu

*/

#include <netdb.h>
#if 0
#	include <linux/in.h>
#endif
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <linux/sockios.h>

#ifdef linux
#	define	NEWSOCKET()	socket(AF_INET, SOCK_PACKET, htons(ETH_P_RARP))
#else
#	define	NEWSOCKET()	socket(SOL_SOCKET, SOCK_RAW, ETHERTYPE_REVARP)
#endif

#define MAC_ADDR_LEN 6
#define IP_ADDR_LEN 4
#define ARP_FRAME_TYPE 0x0806
#define ETHER_HW_TYPE 1
#define IP_PROTO_TYPE 0x0800
#define OP_ARP_REQUEST	1
#define OP_ARP_REPLY	2

struct arp_packet {
        u_char targ_hw_addr[MAC_ADDR_LEN];
        u_char src_hw_addr[MAC_ADDR_LEN];
        u_short frame_type;
        u_short hw_type;
        u_short prot_type;
        u_char hw_addr_size;
        u_char prot_addr_size;
        u_short op;
        u_char sndr_hw_addr[MAC_ADDR_LEN];
        u_char sndr_ip_addr[IP_ADDR_LEN];
        u_char rcpt_hw_addr[MAC_ADDR_LEN];
        u_char rcpt_ip_addr[IP_ADDR_LEN];
        u_char padding[18];
};

static int get_ip_addr(struct in_addr*,char*);
static int get_hw_addr(u_char*,char*);

static int ioctl_sock;

char *
send_arp(const char* minterface)
{
	struct in_addr my_in_addr;
	struct arp_packet pkt;
	struct sockaddr sa;
	int	optypes [] = {OP_ARP_REQUEST, OP_ARP_REPLY, 0}; 
	int sock;
	u_char hwaddr[MAC_ADDR_LEN];
	int	j, i;
	char *ifname, *ifsend, *cindex;
	char ch;

	/* 
  	 * Linux is finicky this way - once you run an ioctl to get
	 * mac address using the socket - you can't use this socket to
	 * send anything. Also, when i try to repeat the ioctl - i can't
	 * send through the other socket either. BIZARRE!!
	 * So I make two sockets and use them as little as possible :)))
	 */
	sock=NEWSOCKET();
	ioctl_sock = NEWSOCKET();

	if ((sock < 0) || (ioctl_sock < 0)) {
		return("ERROR: Unable to create socket");
	}

	/* Most switches/routers respond to the ARP reply, a few only
	 * to an ARP request.  RFCs say they should respond
	 * to either.  Oh well... We'll try and work with all...
	 * So, we broadcast both an ARP request and a reply...
	 * See RFCs 2002 and 826.
	 *
	 * The observation about some only responding to ARP requests
	 * came from Masaki Hasegawa <masaki-h@pp.iij4u.or.jp>.
	 * So, this fix is due largely to him.
	 */
	
	if (!(ifname = strdup(minterface)) || !(ifsend = strdup(minterface)))
		return ("ERROR: Cannot duplicate interface name!");

	/*
	 * For an alias interface we use its data but we send
	 * the actual packet on corresponding real interface
	 */
	if (cindex = strchr(ifsend, ':')) 
		*cindex = '\0';

	if(!get_hw_addr(hwaddr, ifname)) {
		return("ERROR: Unable to find mac address for interface.");
	}
	if(!get_ip_addr(&my_in_addr, ifname)) {
		return("ERROR: Unable to find ip address for interface.");
	}
	
	for (j=0; optypes[j] != 0 ; j++) {
		pkt.frame_type = htons(ARP_FRAME_TYPE);
		pkt.hw_type = htons(ETHER_HW_TYPE);
		pkt.prot_type = htons(IP_PROTO_TYPE);
		pkt.hw_addr_size = MAC_ADDR_LEN;
		pkt.prot_addr_size = IP_ADDR_LEN;
		pkt.op=htons(optypes[j]);

		for (i = 0; i < MAC_ADDR_LEN; i++)
			pkt.targ_hw_addr[i] = 0xff;

		for (i = 0; i < MAC_ADDR_LEN; i++)
			pkt.rcpt_hw_addr[i] = 0xff;

		for (i = 0; i < MAC_ADDR_LEN; i++)
			pkt.src_hw_addr[i] = hwaddr[i];

		for (i = 0; i < MAC_ADDR_LEN; i++)
		 	pkt.sndr_hw_addr[i] = hwaddr[i];

		memcpy(pkt.sndr_ip_addr,&my_in_addr,IP_ADDR_LEN);
		memcpy(pkt.rcpt_ip_addr,&my_in_addr,IP_ADDR_LEN);

		memset(pkt.padding,0, 18);

		strcpy(sa.sa_data, ifsend);
		if (sendto(sock, (const void *)&pkt, sizeof(pkt), 0,	&sa,sizeof(sa)) < 0) {
			return("ERROR: Unable to send arp gratuitous ARP packet");
		}
	}
	return "";
}

static int get_ip_addr(struct in_addr* in_addr,char* str)
{
	struct ifreq ifr;
	struct sockaddr_in sin;

	strcpy(ifr.ifr_name, str);
	ifr.ifr_addr.sa_family = AF_INET;
	if (ioctl(ioctl_sock, SIOCGIFADDR, &ifr)) {
		return 0;
	}

	memcpy(&sin, &ifr.ifr_addr, sizeof(struct sockaddr_in));
	in_addr->s_addr = sin.sin_addr.s_addr;
	return 1;
}

static int get_hw_addr(u_char* buf,char* str)
{

	int i;
	struct ifreq ifr;
	char c,val = 0;

	strcpy(ifr.ifr_name, str);
	if (ioctl(ioctl_sock, SIOCGIFHWADDR, &ifr)) {
		return 0;
	}

	memcpy(buf, ifr.ifr_hwaddr.sa_data, IFHWADDRLEN);
	return 1;
}

/* Stuff below is for ethtoolquery support */

#include "ethtool-util.h"

#ifndef SIOCETHTOOL
#define SIOCETHTOOL     0x8946
#endif
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif

static int do_gset(int fd, struct ifreq *ifr,const char *devname);

static enum {
	MODE_GSET=0,
	MODE_SSET,
	MODE_GDRV,
	MODE_GREGS,
	MODE_NWAY_RST,
	MODE_GEEPROM,
	MODE_SEEPROM,
	MODE_TEST,
	MODE_PHYS_ID,
	MODE_GPAUSE,
	MODE_SPAUSE,
	MODE_GCOALESCE,
	MODE_SCOALESCE,
	MODE_GRING,
	MODE_SRING,
	MODE_GOFFLOAD,
	MODE_SOFFLOAD,
	MODE_GSTATS,
} mode = MODE_GSET;

static int do_gset(int fd, struct ifreq *ifr,const char *devname)
{
	int err;
	struct ethtool_value edata;

	edata.cmd = ETHTOOL_GLINK;
	ifr->ifr_data = (caddr_t)&edata;
	err = ioctl(fd, SIOCETHTOOL, ifr);
	if (err == 0) {
		return edata.data ? 1 : 0;
	} else {
		/* Can not get device settings */	
		return 2;
	}
}

int ethtoolquery(const char *devname)
{
	struct ifreq ifr;
	int fd;
	int x;

	/* Setup our control structures. */
	memset(&ifr, 0, sizeof(ifr));
	strcpy(ifr.ifr_name, devname);

	/* Open control socket. */
	fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (fd < 0) {
		return 2;
	}

	x=do_gset(fd, &ifr,devname);
	shutdown(fd,SHUT_RDWR);
	close(fd);
	return(x);
}
